/*
* Copyright (c) 2017 Zhangyujie (Tocy)

* This file is part of PlayerDemo project.
*/

#include "audioplay.h"

#ifndef LOGE
#define LOGE printf
#define LOGD printf
#endif

uint8_t AudioPlayer::AudioSpec::get_sample_byte()
{
    switch (format)
    {
    case S16:
        return 2;
    case U8:
    case S8:
        return 1;
    default:
        LOGE("%s %d unknown format %d", __FUNCTION__, __LINE__, format);
        return 2;
    }
    return 2;
}

AudioPlayer::AudioSpec::AudioSpec(uint8_t ch/*=2*/, uint32_t sample_fmt/*=S16*/, uint32_t sample_rate/*=48000*/)
: channels(ch), byte_per_sample(2), reserved(0)
, format(sample_fmt),  freq(sample_rate)
{
    byte_per_sample = get_sample_byte();
}

bool AudioPlayer::AudioSpec::operator ==(const AudioSpec & rhs) const
{
    return (channels == rhs.channels && format == rhs.format && freq == rhs.freq);
}
bool AudioPlayer::AudioSpec::operator !=(const AudioSpec & rhs) const
{
    return !(*this == rhs);
}

AudioPlayer::AudioPlayer()
: m_device(INVALID_AUDIO_DEVICE_ID)
, m_audio_buff(NULL), m_buff_size(0)
, m_write_pos(0)
, m_mutex(NULL)
, m_stopped(false)
{
    m_mutex = SDL_CreateMutex();
    if (NULL == m_mutex)
    {
        LOGE("%s %d create failed %s", __FUNCTION__, __LINE__, SDL_GetError());
    }
}

 AudioPlayer::~AudioPlayer()
 {
    uninit();
    if (NULL != m_mutex)
    {
        SDL_DestroyMutex( m_mutex );
        m_mutex = NULL;
    }
 }

bool AudioPlayer::init()
{
    if (INVALID_AUDIO_DEVICE_ID != m_device)
    {
        LOGD("%s %d have inited\n", __FUNCTION__,  __LINE__);
        return true;
    }

    if (SDL_WasInit(SDL_INIT_AUDIO) == 0)
    {
        LOGE("%s %d SDL Audio is not initialized.\n", __FUNCTION__, __LINE__);
        return false;
    }
    m_stopped = false;
    return true;
}
void AudioPlayer::uninit()
{
    m_stopped = true;
    // close device
    close_device();
}

bool AudioPlayer::play(uint8_t * pcm, int pcm_size, AudioSpec & in_spec)
{
    if (INVALID_AUDIO_DEVICE_ID == m_device || m_spec != in_spec)
    {
        m_spec = in_spec;
        close_device();
        // open device
        open_device();
    }

    if (INVALID_AUDIO_DEVICE_ID == m_device)
    {
        LOGE("%s %d open device failed drop pcm %d", __FUNCTION__, __LINE__, pcm_size);
        return false;
    }

    if (pcm_size <= 0 || NULL == pcm)
    {
        return false;
    }

    // save pcm here
    SDL_LockMutex(m_mutex);
    if (m_write_pos + (uint32_t)pcm_size <= m_buff_size)
    {
        SDL_memcpy(m_audio_buff + m_write_pos, pcm, pcm_size);
        m_write_pos += (uint32_t)pcm_size;
    }
    else
    {
        LOGE("%s %d drop pcm %p size %d", __FUNCTION__, __LINE__, pcm, pcm_size);
    }
    SDL_UnlockMutex(m_mutex);
    return true;
}

bool AudioPlayer::open_device()
{
    SDL_AudioSpec want, have;
    SDL_AudioDeviceID dev;

    SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
    SDL_memset(&have, 0, sizeof(have));
    want.freq = m_spec.freq;
    want.format = m_spec.format;
    want.channels = m_spec.channels;
    want.samples = 4096;
    want.callback = sdl_audio_callback;
    want.userdata = this;

    dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
    if (INVALID_AUDIO_DEVICE_ID == dev)
    {
        LOGE("Failed to open audio: %s", SDL_GetError());
        return false;
    }
    else
    {
        if (have.format != want.format || have.freq != want.freq)
        {
            LOGD("We didn't get %d audio format. freq %d", want.format, want.freq);
        }
    }
    LOGD("%s %d get samples %d size %d", __FUNCTION__, __LINE__, have.samples, have.size);
    
    // alloc pcm buffer
    alloc_data_buff();

    m_device = dev;
    SDL_PauseAudioDevice(m_device, 0); /* start audio playing. */
    return true;
}

void AudioPlayer::close_device()
{
    if (INVALID_AUDIO_DEVICE_ID != m_device)
    {
        SDL_PauseAudioDevice(m_device, 1); /* stop audio playing. */
        SDL_CloseAudioDevice(m_device);
        m_device = INVALID_AUDIO_DEVICE_ID;
    }
    free_data_buff();
}

bool AudioPlayer::alloc_data_buff()
{
    if (NULL != m_audio_buff)
    {
        LOGD("%s %d have inited before, warnning\n", __FUNCTION__, __LINE__);
        return true;
    }

    int buf_size_per_second = m_spec.freq * m_spec.byte_per_sample * m_spec.channels;
    m_buff_size = buf_size_per_second * 4; // 最长保存4s pcm数据
    m_audio_buff = new uint8_t[m_buff_size];

    SDL_memset(m_audio_buff, 0, m_buff_size);

    m_write_pos = 0;
    return true;
}
void AudioPlayer::free_data_buff()
{
    if (NULL != m_audio_buff)
    {
        delete [] m_audio_buff;
        m_audio_buff = NULL;
        m_buff_size = 0;
        m_write_pos = 0;
    }
}

int AudioPlayer::fill_audio_buff(uint8_t * stream, int size)
{
    SDL_LockMutex(m_mutex);
    uint32_t fill_size = ((uint32_t)size < m_write_pos) ? (uint32_t)size : m_write_pos;
    SDL_memcpy(stream, m_audio_buff, fill_size);
    if(fill_size == m_write_pos)
    {
        m_write_pos = 0;
    }
    else
    {
        m_write_pos -= fill_size;
        SDL_memmove(m_audio_buff, m_audio_buff + fill_size, m_write_pos);
    }
    SDL_UnlockMutex(m_mutex);

    return fill_size;
}
void AudioPlayer::sdl_audio_callback(void* userdata, Uint8* stream, int len)
{
    if (NULL == userdata)
    {
        LOGE("%s %d null context\n", __FUNCTION__, __LINE__);
        if (len>0)SDL_memset(stream, 0, len);
        return;
    }

    AudioPlayer * ap = (AudioPlayer*)userdata;
    int need_size = len;
    int read_pos = 0;
    while (need_size > 0)
    {
        if(ap->m_stopped)
            break;

        int fill_size = ap->fill_audio_buff(stream + read_pos, need_size);
        if (fill_size < 0)
        {
            LOGE("%s %d fill_audio_buff ret %d out", __FUNCTION__, __LINE__, fill_size);
            break;
        }
        read_pos += fill_size;
        need_size -= fill_size;

        // for not read any input data
        if (0 == fill_size)
        {
            SDL_Delay(1);
        }
    }

    // fill remained data to zero
    if (need_size < len)
    {
        SDL_memset(stream+read_pos, 0, need_size);
    }
}